﻿// <copyright file="SimulationEngine.cs" company="Marcin Wosik">
// Copyright (c) 2010 All Right Reserved
// </copyright>
// <author>Marcin Wosik</author>
// <email>mawo11@gmail.com</email>
// <date>2010-02-25</date>
// <summary></summary>
namespace GameOfLife.GameData
{
    using System;
    using System.ComponentModel;
    using System.Threading;
    using System.Windows.Controls;
    using System.Windows.Threading;
    using GameOfLife.Interfaces;

    internal sealed class SimulationEngine : INotifyPropertyChanged
    {
        #region Fields

        private byte[] BornCondition;
        private byte[] LifeCondition;
        private DispatcherTimer Timer;
        private SimalutionEngineStatus status;

        #endregion Fields

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        internal event EventHandler EndGame;

        internal event EventHandler RedrawBoard;

        #endregion Events

        #region Properties

        internal GameSettings Data
        {
            get;
            set;
        }

        internal bool IsRunning
        {
            get { return this.Timer != null && this.Timer.IsEnabled; }
        }

        internal SimalutionEngineStatus Status
        {
            get
            {
                return this.status;
            }
            set
            {
                this.status = value;
                this.NotifyPropertyChanged("Status");
            }
        }

        #endregion Properties

        #region Methods

        internal void ChangeTimer()
        {
            this.Timer.Interval = new TimeSpan(0, 0, 0, 0, this.Data.TimerStep);
        }

        internal bool PrepareSimulation()
        {
            if (string.IsNullOrEmpty(this.Data.Rule))
            {
                return false;
            }

            return this.ParseRule();
        }

        internal bool Start()
        {
            if (!this.PrepareSimulation())
            {
                return false;
            }

            // start clock
            this.StartTimer();
            this.Status = SimalutionEngineStatus.Running;
            return true;
        }

        internal void Step()
        {
            if (this.PrepareSimulation())
            {
                this.Status = SimalutionEngineStatus.Running;
                this.DoStep();
                this.Status = SimalutionEngineStatus.Stoped;
            }
        }

        internal void Stop()
        {
            this.StopTimer();
            this.Status = SimalutionEngineStatus.Stoped;
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void DoStep()
        {
            int elements = 0;

            try
            {
                lock (this.Data.Elements)
                {
                    // count life elements
                    // calculating new status for current element
                    for (int e = 0; e < this.Data.Elements.Count; ++e)
                    {
                        // IElement element = Data.Elements[x][y];
                        IElement element = this.Data.Elements[e];
                        if (element != null)
                        {
                            byte countElement = 0;
                            for (int i = 0; i < element.ConnectedElements.Count; ++i)
                            {
                                if (element.ConnectedElements[i] != null && element.ConnectedElements[i].IsAlive)
                                {
                                    countElement++;
                                }
                            }

                            if (element.IsAlive)
                            {
                                if (Array.IndexOf<byte>(this.LifeCondition, countElement) == -1)
                                {
                                    element.NewState = ElementState.Dead;
                                }
                                else
                                {
                                    // dalej zyje
                                    if (element.Current == ElementState.Created)
                                    {
                                        element.NewState = ElementState.Existing;
                                        elements++;
                                    }
                                }
                            }
                            else
                            {
                                // sprawdzenie czy jesli pusty lub martwy to utworzenie nowego
                                if (Array.IndexOf<byte>(this.BornCondition, countElement) != -1)
                                {
                                    element.NewState = ElementState.Created;
                                    elements++;
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                this.Stop();
                ChildWindow errorWin = new ErrorWindow(ex);
                errorWin.Show();
            }

            if (this.RedrawBoard != null)
            {
                this.RedrawBoard(this, null);
            }

            // if size of element is zero then end game
            if (elements == 0 && this.EndGame != null)
            {
                this.Stop();
                this.EndGame(this, null);
            }
        }

        private void NotifyPropertyChanged(string info)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        private bool ParseRule()
        {
            string temp;
            //Rule 011/3
            string[] parts = this.Data.Rule.Split('/');
            if (parts.Length >= 1)
            {
                temp = parts[0];
                this.LifeCondition = new byte[temp.Length];
                for (int i = 0; i < temp.Length; ++i)
                {
                    byte val = 0;
                    if (byte.TryParse(temp.Substring(i, 1), out val))
                    {
                        this.LifeCondition[i] = val;
                    }
                }
            }

            if (parts.Length > 2)
            {
                return false;
            }

            temp = parts[1];
            if (!string.IsNullOrEmpty(temp))
            {
                this.BornCondition = new byte[temp.Length];
                for (int i = 0; i < temp.Length; ++i)
                {
                    byte val = 0;
                    if (byte.TryParse(temp.Substring(i, 1), out val))
                    {
                        this.BornCondition[i] = val;
                    }
                }
            }
            else
            {
                this.BornCondition = new byte[1] { 255 };
            }
            return true;
        }

        private void StartTimer()
        {
            if (this.Timer == null)
            {
                this.Timer = new DispatcherTimer();
                this.Timer.Tick += (s, e) => this.DoStep();
                this.ChangeTimer();

            }
            this.Timer.Start();
        }

        private void StopTimer()
        {
            if (this.Timer != null)
            {
                this.Timer.Stop();
            }
        }

        #endregion Methods
    }
}